/**
 * Simbolos para añadir a la tabla de simbolos
 * @author paco
 */
public class Simbolo implements Comparable<Simbolo>
{
    //Clase de simbolos

    public static final int VAR = 1,
            METODO = 2,
            CLASE = 3,
            INDICE = 4,
            INTERNO = 5;

    /**
     * Nombre del simbolo
     */
    public String nombre;
    /**
     * Tipo del simbolo, metodo, parametro...
     */
    public int tipoSimbolo;
    /**
     * Tipo del simbolo entero, double, boolean...
     */
    public int tipo;
    /**
     * Identificador del simbolo
     */
    public int id;
    /**
     * Indica si el simbolo ha sido inicializado o no
     */
    public boolean inicializado;
    /**
     * Indica si el simbolo es usado a lo largo del programa
     */
    private boolean usado;
    /**
     * Cuerpo del simbolo en caso de ser un metodo o semejante
     */
    private String cuerpo;
    /**
     * Indica la visibilidad public o private del simbolo
     */
    private boolean isPublic;
    /**
     * Indica si el simbolo actual es un metodo
     */
    private boolean isMetodo;
    /**
     * Indica si el simbolo actual es un campo
     */
    private boolean isCampo;
    /**
     * En caso de ser un metodo, indica si tiene return o no
     */
    private boolean tieneReturn;
    /**
     * Indica si el simbolo es un parametro
     */
    private boolean isParam;
    /**
     * En caso de ser un metodo, contenido de sus locales
     */
    private String locals;
    /**
     * En caso de ser un método número de parámetros que contien
     */
    public int parametros;
    /**
     * Idinca si es una clase
     */
    public boolean isClase;
    /**
     * Indica si es constructor
     */
    public boolean isConstructor;

    /**
     * Constructor
     * @param nombre Nombre del simbolo
     * @param tipoSimbolo Indica si es un metodo, un vector...
     * @param tipo Indica si es un double, entero, booleano...
     * @param id Identificador del simbolo
     */
    public Simbolo(String nombre, int tipoSimbolo, int tipo, int id)
    {
        this.nombre = nombre;
        this.tipoSimbolo = tipoSimbolo;
        this.tipo = tipo;
        this.id = id;
        this.inicializado = false;
        this.isCampo = false;
        this.isMetodo = false;
        this.parametros = 0;
        this.locals = "";
        this.isClase = false;
        this.isConstructor = false;
        this.isPublic = false;
    }

    /**
     * Constructor
     * @param nombre Nombre del simbolo
     * @param tipoSimbolo Tipo del simbolo que se añade, metodo, campo, ...
     * @param isPublic Visibilidad del simbolo, publica en caso de ser cierto y private en caso contrario
     */
    public Simbolo(String nombre, int tipoSimbolo, boolean isPublic)
    {
        this.nombre = nombre;
        this.tipoSimbolo = tipoSimbolo;
        this.isPublic = isPublic;
        this.inicializado = false;
        this.isCampo = false;
        this.isMetodo = false;
        this.tieneReturn = false;
        this.parametros = 0;
        this.locals = "";
        this.isClase = false;
        this.isConstructor = false;
        this.isPublic = false;
    }

    /**
     * Constructor
     * @param nombre Nombre del simbolo
     * @param tipoSimbolo Tipo del simbolo que se añade, metodo, campo, ...
     * @param cuerpo Cuerpo del simbolo en caso de ser un metodo o semejante
     */
    public Simbolo(String nombre, int tipoSimbolo, String cuerpo)
    {
        this.nombre = nombre;
        this.tipoSimbolo = tipoSimbolo;
        this.cuerpo = cuerpo;
        this.inicializado = false;
        this.isCampo = false;
        this.isMetodo = false;
        this.tieneReturn = false;
        this.isParam = false;
        this.parametros = 0;
        this.locals = "";
        this.isClase = false;
        this.isConstructor = false;
        this.isPublic = false;
    }
    
    public void setClase()
    {
        this.isClase = true;
    }
    
    public boolean isPublic()
    {
        return this.isPublic;
    }
    
    /**
     * Tipo de simbolo double, boolean, ...
     * @return Tipo del simbolo
     */
    public int getTipo()
    {
        return this.tipo;
    }
    
    /*
     * Tipo de un simbolo
     */
    public int getTipoSimbolo()
    {
        return this.tipoSimbolo;
    }
    
    /**
     * Cambia el número de parametros que recibe un metodo.
     * @param numParametros Numero de parametros del emtodo
     */
    public void setNumParametros( int numParametros )
    {
        this.parametros = numParametros;
    }
    
    public void setConstructor()
    {
        this.isConstructor = true;
    }
    
    public void setId( int id )
    {
        this.id = id;
    }

    /**
     * Recupera el número de parámetros de un simbolo del tipo metodo
     * @return Número de parámetros
     */
    public Integer getNumParametros()
    {
        if ( !this.isMetodo )
            return null;
        else
            return parametros;
    }

    /**
     * Hace que el simbolo sea un parámetro
     */
    public void setParam()
    {
        this.isParam = true;
    }

    /**
     * El simbolo deja de ser parámetro
     */
    public void resetParam()
    {
        this.isParam = false;
    }

    /**
     * Cambia el cuerpo del simbolo
     * @param cuerpo Nuevo cuerpo
     */
    public void setCuerpo( String cuerpo )
    {
        this.cuerpo = cuerpo;
    }

    /**
     * Recupera el cuerpo de un metodo.
     * @return Cadena con el cuerpo
     */
    public String getCuerpo()
    {
        return this.cuerpo;
    }

    /**
     * Cambia las variables locales de un metodo
     * @param locals Cadena con el .locals
     */
    public void setLocals( String locals )
    {
        this.locals = locals;
    }

    /**
     * Recupera el .locals
     * @return Cadena con el .locals de un metodo.
     */
    public String getLocals()
    {
        return locals;
    }

    /**
     * Comprueba si el simbolo es un metodo
     * @return True si el simbolo es un metodo.
     */
    public boolean isMetodo()
    {
        return this.isMetodo;
    }

    /**
     * Comprueba si el simbolo es un campo
     * @return True sólo si el simbolo es un campo
     */
    public boolean isCampo()
    {
        return this.isCampo;
    }

    /**
     * Comprueba si un metodo tiene un retorno
     * @return Cierto si el simbolo tiene un retorno
     */
    public boolean tieneReturn()
    {
        return this.tieneReturn;
    }

    /**
     * Indica que el simbolo tiene un retorno
     */
    public void setReturn()
    {
        tieneReturn = true;
    }

    /**
     * Hace al simbolo publico
     */
    public void setPublic()
    {
        this.isPublic = true;
    }

    /**
     * El simbolo deja de ser publico
     */
    public void resetPublic()
    {
        this.isPublic = false;
    }
    
    public boolean isConstructor()
    {
        return this.isConstructor;
    }

    /**
     * El simbolo es un campo
     */
    public void setCampo()
    {
        this.isCampo = true;
    }

    /**
     * El simbolo deja de ser un campo
     */
    public void resetCampo()
    {
        this.isCampo = false;
    }

    /**
     * El simbolo es un metodo
     */
    public void setMetodo()
    {
        this.isMetodo = true;
    }

    /**
     * El simbolo deja de ser un metodo
     */
    public void resetMetodo()
    {
        this.isMetodo = false;
    }

    /**
     * Escribe los simbolos que son un campo dentro de una clase
     * @param padre Nombre de la clase en la que están
     * @param isArray Indica si el simbolo es un array
     * @param tipoBase En caso se ser un array indica su tipo base
     * @return Cadena con el .field
     */
    public String escribeCampo(String padre, boolean isArray, int tipoBase)
    {
        String s = ".field ";

        if (isPublic)
        {
            s += "public ";
        } else
        {
            s += "private ";
        }

        if (tipo == Tipo.INT || tipo == Tipo.BOOLEAN)
        {
            s += "int32 ";
        } else if (tipo == Tipo.DOUBLE)
        {
            s += "float64 ";
        } else if (isArray)
        {
            if (tipoBase == Tipo.INT || tipoBase == Tipo.BOOLEAN)
            {
                s += "int32[] ";
            } else
            {
                s += "float64[] ";
            }
        } else
        {
            s += "class '" + padre + "' ";
        }

        s += "'" + nombre + "'\n";

        return s;
        //return ".field"  + pub|pri + int32|float64|class 'nombreClase' 'nombreVar';
    }

    public void setTipo( int tipo )
    {
        this.tipo = tipo;
    }
    
    public String escribeMain()
    {
        return ".method static public void main () cil managed { \n .entrypoint \n";
    }
    
    /**
     * Escribe la salida CIL de un método empezand con el .method
     * @param parametros Numero de parametros
     * @param locals Cadena con el .locals
     * @return Cadena con el .method
     */
    public String escribeMetodo( int parametros, String locals)
    {
        String s ="";
               
        if ( this.getNombre().equals("Main") )
            s =  escribeMain();
        else
        {
            s = ".method public ";

            if ( tipo == Tipo.INT || tipo == Tipo.BOOLEAN )
                s += "int32 ";
            else if ( tipo == Tipo.DOUBLE )
                s += "float64 ";
            else if ( tipo == -1 )
                s += "";
            else
                s += "class ";
            
            s+= "'" + this.getNombre() + "' (";


            int numPar = 0;

            if ( getNumParametros() == null )
                parametros = 0;
            while ( numPar < parametros )
            {
                s += "float64";
                numPar++;
                if ( numPar < getNumParametros() )
                    s+= ",";
            }
            s += ") cil managed {\n";
        }
        s += locals + this.getCuerpo() + "\n}\n";

        
        return s;
    }
    
    public String escribeConstructor()
    {
        String s = "";
        
        s += ".method public specialname rtspecialname instance\n";
        s += "void .ctor (";
        
        int numPar = 0;
        if ( getNumParametros() == null )
            parametros = 0;
        while ( numPar < parametros )
        {
            s += "float64";
            numPar++;
            if ( numPar < getNumParametros() )
                s+= ",";
        }
        s += ") cil managed {\n";
        s += "ldarg 0\n";
        s += "call instance void [mscorlib]System.Object::.ctor()\n";
        s += locals + this.getCuerpo() + "\n}\n";
        
        return s;
    }
    

    /**
     * Escribe un simbolo
     * @return Cadena con el formato de un simbolo.
     */
    public String ToString()
    {
        String s = "";
        if (this.tipo == Tipo.DOUBLE)
        {
            s = "float64 ";
        } else if ( this.tipo == Tipo.BOOLEAN || this.tipo == Tipo.INT)
        {
            s = "int32 ";
        }

        //s += this.getClase() + this.getNombre();

        return s;

    }

    /**
     * Obtiene el nombre de un simbolo
     * @return Cadena con el nombre del simbolo
     */
    public String getNombre()
    {
        return this.nombre;
    }

    /**
     * Indica que un simbolo ha sido usado
     */
    public void setUsado()
    {
        usado = true;
    }

    /**
     * Indica que un simbolo ha sido inicializado
     */
    public void inicializar()
    {
        this.inicializado = true;
    }

    /**
     * Comprueba si un simbolo ha sido inicializado en el codigo
     * @return Cierto si ha sido inicializado
     */
    public boolean isInicializado()
    {
        return this.inicializado;
    }

    /**
     * Escribe un simbolo
     * @param nivel Nivel que se quiere buscar
     * @return Cadena con el formato del simbolo
     */
    public String ToString(int nivel)
    {
        String s = "";
        for (int i = 0; i < nivel * 4; i++)
        {
            s += ' ';
        }
        if ( usado && tipoSimbolo == VAR )
        {
            if ( tipo == Tipo.INT )
            {
                return s + "int " + nombre + ";\n";
            }
            if ( tipo == Tipo.DOUBLE )
            {
                return s + "double " + nombre + ";\n";
            }
        }
        if (usado && tipoSimbolo == METODO)
        {
            return s + cuerpo;
        }
        return "";
    }

    /**
     * Comprueba si un simbolo es del tipo array
     * @return Cierto si el simbolo es un array
     */
    public boolean isArray()
    {
        return ( this.tipo != Simbolo.CLASE && this.tipo != Simbolo.METODO && this.tipo > 3     );
    }
    
    /**
     * Comprueba si n simbolo es una clase
     * @return Cierto si el simbolo es una clase y falso en caso contrario.
     */
    public boolean isClass()
    {
        return ( this.isClase );
    }

    /**
     * Compara dos simbolos
     * @param s Simbolo que se quiere comparar con el actual
     * @return Devuelve 0 si sus id son iguales, -1 si es mas pequeño o 1 si es más grande
     */
    public int compareTo(Simbolo s)
    {
        if (this.id == s.id)
        {
            return 0;
        } else if (this.id > s.id)
        {
            return 1;
        } else
        {
            return -1;
        }
    }
    
    public boolean isParam()
    {
        return this.isParam;
    }
    
    public boolean isParametros()
    {
        return this.isParam;
    }
}
